﻿using MongoDB.Bson;
using MongoDB.Driver;
using MongoDB.Driver.Linq;
using Sustainalytics.Auth.Claims;
using Sustainalytics.BusinessResearchProductStates.Entities.ResearchState;
using Sustainalytics.ClientDW.Entities;
using Sustainalytics.Controversies.Service.Models;
using Sustainalytics.ESG.Entities;
using Sustainalytics.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Dynamic;
using System.Linq.Expressions;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Web.Http;
using System.Web.Http.Cors;

namespace Sustainalytics.Controversies.Service.Controllers
{
    [EnableCors(origins: "*", headers: "*", methods: "*")]
    [RoutePrefix("api/v1/companies")]
    public class ListingController : ApiController
    {
        private readonly IMongoCollection<CorporateData> _corporateDataCollection;
        private readonly IMongoCollection<RPControversyIndicator> _rpControversyIndicatorCollection;

        public ListingController(
            IMongoCollection<CorporateData> corporateDataCollection,
            IMongoCollection<RPControversyIndicator> rpControversyIndicatorCollection)
        {
            _corporateDataCollection = corporateDataCollection;
            _rpControversyIndicatorCollection = rpControversyIndicatorCollection;
        }

        [HttpGet]
        [Route("")]
        [InjectUniverse("universeIds", AccountFeature.Controversies)]
        public async Task<IHttpActionResult> Get(
           [FromUri] ControversiesListFilter filters,
           int skip = 0,
           int take = 20,
           bool getCount = false,
           bool orderAscending = true,
           string orderBy = "CompanyName",
           [FromUri]int[] universeIds = null)
        {
            var companiesPerformance = await _rpControversyIndicatorCollection
                .Aggregate()
                .Match(Builders<RPControversyIndicator>.Filter.And(GetFilterOptionsForPerformance(filters)))
                .SortBy(x=> x.CompanyId)
                .ThenByDescending(x=> x.AnswerCategory)
                .Group(x => x.CompanyId, g => new
                {
                    CompanyId = g.Key,
                    g.First().AnswerCategory,
                    g.First().AnswerCategoryText
                })
                .ToListAsync()
                .ConfigureAwait(false);

            var dictionaryCompaniesPerformance = companiesPerformance.ToDictionary(x=> x.CompanyId);

            var options = new FindOptions() { Collation = new Collation(locale: "en", caseLevel: true, strength: CollationStrength.Primary) };
            var filter = 
                Builders<CorporateData>.Filter.Eq(x => x.ControversyResearchState.ResearchState, ResearchState.ResearchAvailable) &
                Builders<CorporateData>.Filter.And(GetFilterOptionsForCompanyData(filters)) &
                Builders<CorporateData>.Filter.In(x => x.Id, dictionaryCompaniesPerformance.Keys);

            if (getCount)
            {
                //get companies paged
                var count = await _corporateDataCollection
                    .Find(filter, options)
                    .CountAsync()
                    .ConfigureAwait(false);

                return Ok(new { companyCount = count });
            }

            var companiesList = await _corporateDataCollection
                .Find(filter, options)
                .Project<CorporateData>(Builders<CorporateData>.Projection
                    .Include(x => x.Id)
                    .Include(x => x.CompanyName)
                    .Include(x => x.MainExchange)
                    .Include(x=>x.MainTicker)
                    .Include(x => x.SubPeerGroup)
                    .Include(x => x.UniverseIds)) 
                .Sort(GetCorporateDataSortDefinition(orderAscending, orderBy))
                .Skip(skip)
                .Limit(take)
                .ToListAsync()
                .ConfigureAwait(false);

            var result = companiesList.Select(x => new
            {
                x.Id,
                x.CompanyName,
                Exchange = x.MainExchange,
                Ticker = x.MainTicker,
                x.SubPeerGroup,
                Performance = dictionaryCompaniesPerformance[x.Id].AnswerCategoryText,
                IsEnabled = universeIds.Any(y => x.UniverseIds.Contains(y))
            });

            return Ok(result);
        }

      
        private static SortDefinition<CorporateData> GetCorporateDataSortDefinition(bool orderAscending, string orderBy)
        {
            return orderAscending ?
                Builders<CorporateData>.Sort.Ascending(sortDictionary[orderBy]) :
                Builders<CorporateData>.Sort.Descending(sortDictionary[orderBy]);
        }


        private static readonly Dictionary<string, Expression<Func<CorporateData, object>>> sortDictionary = new Dictionary<string, Expression<Func<CorporateData, object>>>
        {
            { "CompanyName", x => x.CompanyName },
            { "Exchange", x => x.MainExchange },
            { "Ticker", x => x.MainTicker },
            { "Subindustry", x => x.SubPeerGroup },
            { "Performance", x => x.CompanyName }
        };

        private List<FilterDefinition<CorporateData>> GetFilterOptionsForCompanyData(ControversiesListFilter filters)
        {
            var filtersCompanies = Builders<CorporateData>.Filter;
            var governanceFiltersList = new List<FilterDefinition<CorporateData>> { FilterDefinition<CorporateData>.Empty };

            if (!filters.CompanyName.IsNullOrEmpty())
            {
                var filterCompany = filtersCompanies.Regex("CompanyName", new BsonRegularExpression(Regex.Escape(filters.CompanyName.ToLowerInvariant()), "i"));
                governanceFiltersList.Add(filterCompany);
            }

            if (!filters.SubPeerGroup.IsNullOrEmpty())
            {
                var filterSubPeerGroup = filtersCompanies.In(x => x.SubPeerGroup, filters.SubPeerGroup);
                governanceFiltersList.Add(filterSubPeerGroup);
            }

            return governanceFiltersList;
        }

        private List<FilterDefinition<RPControversyIndicator>> GetFilterOptionsForPerformance(ControversiesListFilter filters)
        {
            var filtersCompanies = Builders<RPControversyIndicator>.Filter;
            var rpControversyIndicatorFilters = new List<FilterDefinition<RPControversyIndicator>> { FilterDefinition<RPControversyIndicator>.Empty };

            if (!filters.Assessments.IsNullOrEmpty())
            {
                var filterAssessments = filtersCompanies.In(x => x.AnswerCategory, filters.Assessments);
                rpControversyIndicatorFilters.Add(filterAssessments);
            }

            return rpControversyIndicatorFilters;
        }
    }
}
